<template>
  <div class="container" ref="container">
    <div class="box"></div>
  </div>
</template>
<script setup lang="ts">
import * as THREE from 'three'
// 导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls'
// 引入性能监视器
import Stats from 'three/examples/jsm/libs/stats.module.js'
// 导入动画库
import gsap from 'gsap'
// 导入dat.gui
import * as dat from 'dat.gui'

import { onMounted, ref } from 'vue'
// import { RouterLink, RouterView } from 'vue-router'

const objects: any = []
let moveForward = false
let moveBackward = false
let moveLeft = false
let moveRight = false
let canJump = false
const velocity = new THREE.Vector3()
const direction = new THREE.Vector3()
let prevTime = performance.now()
let ballColor = '#409EFF'
let boardColor = '#FFFFFF'
const container: any = ref(null)
// 创建性能监视器
const stats = new (<any>Stats)()

// 创建场景
const scene = new THREE.Scene()
scene.background = new THREE.Color('rgb(0,47,167)')

// 创建相机
const camera = new THREE.PerspectiveCamera(
  100,
  window.innerWidth / (window.innerHeight - 1),
  0.1,
  1000
)
// 默认情况下，当我们调用scene.add()的时候，物体将会被添加到(0,0,0)坐标。
// 但将使得摄像机和立方体彼此在一起。为了防止这种情况的发生，我们只需要将摄像机稍微向外移动一些即可。
camera.position.set(0, 10, 100)

// 坐标网格
const gridHelper = new THREE.GridHelper(
  1000,
  100,
  new THREE.Color('#F7F7F7'),
  new THREE.Color('#F7F7F7')
)
scene.add(gridHelper)
// 矢量平面（也可做坐标网格）
// let floorGeometry = new THREE.PlaneGeometry(1000, 1000, 100, 100)
// // floorGeometry.setAttribute('color',)
// const floorMaterial = new THREE.MeshBasicMaterial({
//   color: '#7FFF00',
//   side: THREE.DoubleSide,
//   wireframe: true,
// })
// floorGeometry.rotateX(-Math.PI / 2)
// const floor = new THREE.Mesh(floorGeometry, floorMaterial)
// scene.add(floor)

// 球
for (let i = 0.3; i < 0.9; i += 0.3) {
  const ballGeometry = new THREE.SphereGeometry(i, 64, 64)
  const ballMaterial = new THREE.MeshBasicMaterial({
    color: ballColor
  })
  const ball = new THREE.Mesh(ballGeometry, ballMaterial)
  ball.translateY(20 * i)
  gsap.to(ball.position, {
    y: 17 * i,
    yoyo: true,
    duration: 2,
    ease: 'power1.inOut',
    repeat: -1 //repeat重复次数，-1表示无限次
  })
  scene.add(ball)
}
// 往大屏上增加纹理贴图
// 顺序  右、左、上、下
let arr = ['', '', '', '', '前面', '后面']
let boardMaterial: Array<any> = []
const textTure = new THREE.TextureLoader()
arr.forEach((item) => {
  if (item) {
    const textTureImg = textTure.load(`./简历${item}.png`)
    boardMaterial.push(
      new THREE.MeshBasicMaterial({
        color: boardColor,
        // side: THREE.DoubleSide,  //双面可见
        map: textTureImg
      })
    )
  } else {
    boardMaterial.push(
      new THREE.MeshBasicMaterial({
        color: boardColor
        // side: THREE.DoubleSide,
      })
    )
  }
})
// 简历板
const boardGeometry = new THREE.BoxGeometry(60, 40, 1)
const board = new THREE.Mesh(boardGeometry, boardMaterial)
board.position.y = 40.5
scene.add(board)
gsap.to(board.position, {
  y: 39.5,
  yoyo: true,
  duration: 2,
  ease: 'power1.inOut',
  repeat: -1 //repeat重复次数，-1表示无限次
})

// 增加障碍物
const boxGeometry1 = new THREE.BoxGeometry(40, 10, 20)
const boxMaterial = new THREE.MeshLambertMaterial({
  color: '#C0C4CC'
  // transparent: true,
  // opacity: 0.5,
  // side: DoubleSide,
})
const box1 = new THREE.Mesh(boxGeometry1, boxMaterial)
box1.position.set(0, 5, 40)
scene.add(box1)
objects.push(box1)
const boxGeometry2 = new THREE.BoxGeometry(40, 20, 20)
const box2 = new THREE.Mesh(boxGeometry2, boxMaterial)
box2.position.set(0, 10, 20)
scene.add(box2)
objects.push(box2)

// 创建渲染器
const renderer = new THREE.WebGLRenderer({
  precision: 'highp', //着色器精度
  antialias: true //抗锯齿
})
// 设置渲染器尺寸
renderer.setSize(window.innerWidth, window.innerHeight - 1)

/**
 * 创建第一人称视角控制
 */
// 锁定指针控制器
const pointerLockControls = new PointerLockControls(camera, renderer.domElement)
window.addEventListener('click', function () {
  pointerLockControls.lock()
})
pointerLockControls.addEventListener('lock', function () {
  console.log('lock')
})
pointerLockControls.addEventListener('unlock', function () {
  console.log('unlock')
})
scene.add(pointerLockControls.getObject())

const onKeyDown = function (event: any) {
  console.log(event.code)
  switch (event.code) {
    case 'ArrowUp':
    case 'KeyW':
      moveForward = true
      break
    case 'ArrowLeft':
    case 'KeyA':
      moveLeft = true
      break
    case 'ArrowDown':
    case 'KeyS':
      moveBackward = true
      break
    case 'ArrowRight':
    case 'KeyD':
      moveRight = true
      break
    case 'Space':
      if (canJump === true) velocity.y += 350
      canJump = false
      break
    case 'PageUp':
      board.rotation.y -= Math.PI / 10
      break
    case 'PageDown':
      board.rotation.y += Math.PI / 10
      break
  }
}
const onKeyUp = function (event: any) {
  switch (event.code) {
    case 'ArrowUp':
    case 'KeyW':
      moveForward = false
      break
    case 'ArrowLeft':
    case 'KeyA':
      moveLeft = false
      break
    case 'ArrowDown':
    case 'KeyS':
      moveBackward = false
      break
    case 'ArrowRight':
    case 'KeyD':
      moveRight = false
      break
  }
}

let raycaster: any = new THREE.Raycaster(
  new THREE.Vector3(),
  new THREE.Vector3(0, -1, 0),
  0,
  10
)
// const clock = new THREE.Clock()
function animate() {
  // const spt = clock.getDelta() * 1000 //毫秒
  // console.log('两帧渲染时间间隔(毫秒)', spt)
  // console.log('帧率FPS', 1000 / spt)
  // 渲染下一帧的时候调用animate()方法
  requestAnimationFrame(animate)
  // 如果当前是锁定的，就要进行第一视角的移动
  if (pointerLockControls.isLocked === true) {
    // 首先设置射线 用来做碰撞测试
    // 射线的初始位置就是第一人称对象的位置 那么他的方向就是我前面对象的方向
    raycaster.ray.origin.copy(pointerLockControls.getObject().position)
    raycaster.ray.origin.y -= 10
    // 查看是否相交 如果相交将所有物体放入intersections这个变量
    const intersections = raycaster.intersectObjects(objects, false)
    // console.log('--------》', intersections)
    // 如果intersections.length大于0 则说明他与下面的物体相交了 这时将onObject设置为true
    const onObject = intersections.length > 0
    // 每次都获取上一次的间隔时间 因为根据性能不同每次调用循环函数的时间都是不一样的
    const time = performance.now()
    const delta = (time - prevTime) / 1000
    // 设置一下（10）减速因子 他越快整个过程中速度就会越慢 减速的过程就会越快
    velocity.x -= velocity.x * 10.0 * delta
    velocity.z -= velocity.z * 10.0 * delta
    velocity.y -= 9.8 * 100.0 * delta // y轴跳跃的速度 100.0 = mass
    // console.log('velocity:', velocity)
    // 鼠标操作的时候控制方向 的变量
    // 如果正数就是往前 负数就是往后
    direction.z = Number(moveForward) - Number(moveBackward)
    direction.x = Number(moveRight) - Number(moveLeft)
    direction.normalize() // 确保各个方向的一致运动
    // console.log('direction:', direction)
    //注意velocity向量，这是一个缓冲值，为了保证鼠标抬起后，场景不直接暂停，而是有一个简短的过渡效果：
    if (moveForward || moveBackward) velocity.z -= direction.z * 400.0 * delta
    if (moveLeft || moveRight) velocity.x -= direction.x * 400.0 * delta
    // 如果onObject为true，
    if (onObject === true) {
      // 此时需要将y轴的速度设置为0和之前速度的最大值
      velocity.y = Math.max(0, velocity.y)
      canJump = true
    }
    // 计算每次在x,y,z轴移动的距离
    pointerLockControls.getObject().translateX(-velocity.x * delta)
    pointerLockControls.getObject().position.y += velocity.y * delta
    pointerLockControls.getObject().translateZ(velocity.z * delta)
    // 为了保证我永远是高于地面的 所以我设置当y小于10时就让y等于10
    if (pointerLockControls.getObject().position.y < 10) {
      velocity.y = 0
      pointerLockControls.getObject().position.y = 10
      canJump = true
    }
    prevTime = time
  }
  renderer.render(scene, camera)
  stats.update()
}

// 画面尺寸变化
window.addEventListener('resize', () => {
  console.log('尺寸变化')
  // 摄像机视锥体的长宽比，通常是使用画布的宽/画布的高。默认值是1（正方形画布）。
  camera.aspect = window.innerWidth / (window.innerHeight - 1)
  camera.updateProjectionMatrix()
  //渲染器宽高比
  renderer.setSize(window.innerWidth, window.innerHeight - 1)
  renderer.setPixelRatio(window.devicePixelRatio)
})

// 双击进入全屏
window.addEventListener('dblclick', () => {
  const fullscreenElement = document.fullscreenElement
  if (!fullscreenElement) {
    // 让画布对象全屏
    renderer.domElement.requestFullscreen()
  } else {
    // 使用document退出全屏
    document.exitFullscreen()
  }
})

// 监听键盘按键
document.addEventListener('keydown', onKeyDown)
document.addEventListener('keyup', onKeyUp)

/**
 * 光源
 */
// 平行光
//#region
const directionalLight = new THREE.DirectionalLight(0x00ffff)
const directionalLightHelper = new THREE.DirectionalLightHelper(
  directionalLight,
  5
)
// 设置光源方向
directionalLight.position.set(80, 100, 50)
directionalLightHelper.update() //调用更新才会显示出光线
scene.add(directionalLightHelper)
scene.add(directionalLight)

// // 点光源
// const pointLight = new THREE.PointLight(0xff00ff)
// const pointLightHelper = new THREE.PointLightHelper(pointLight, 5)
// pointLight.position.set(-80, 100, -50)
// scene.add(pointLightHelper)
// scene.add(pointLight)
//#endregion
// 环境光 没有特定方向，只是整体改变场景的光照明暗
const ambient = new THREE.AmbientLight(0xffffff)
scene.add(ambient)

//#region
// // 使用dat.gui
// const gui = new dat.GUI()
// // 移动位置
// gui
//   .add(cube.position, 'x', 0, 10, 0.001)
//   .name('向x轴移动')
//   .onChange((val: number) => {
//     console.log('val:', val)
//   })
// // 修改颜色
// const params1 = {
//   color: '#ffff00',
// }
// gui
//   .addColor(params1, 'color')
//   .name('修改颜色')
//   .onChange((color: string) => {
//     cube.material.color.set(color)
//   })
// // 设置选项框
// gui.add(cube, 'visible').name('是否显示')
// // // 设置函数
// const params2 = {
//   fn: () => {
//     // 设置一个函数触发动画
//     gsap.to(cube.position, { x: 3, duration: 3, repeat: -1, yoyo: true })
//   },
// }
// // gui.add(params2, 'fn').name('点击让物体运动')

// // 添加文件夹
// const folder = gui.addFolder('自定义文件夹名称')
// // 将gui替换为folder即可
// folder.add(cube.material, 'wireframe')
// folder
//   .add(cube.position, 'x', 0, 10, 0.001)
//   .name('向x轴移动')
//   .onChange((val: number) => {
//     console.log('val:', val)
//   })
// folder
//   .addColor(params1, 'color')
//   .name('修改颜色')
//   .onChange((color: string) => {
//     console.log('颜色被修改')
//     cube.material.color.set(color)
//   })
// folder.add(params2, 'fn').name('点击让物体运动')
//#endregion

// 页面挂载后渲染
onMounted(() => {
  console.log(container.value)
  container.value?.appendChild(renderer.domElement)
  container.value?.appendChild(stats.domElement) //性能监视器
  animate()
})
</script>
<style scoped>
.box {
  width: 100px;
  height: 100px;
  position: absolute;
}
</style>
